package com.xueliman.iov.auth_server_resource_common.config;


import cn.hutool.core.lang.ClassScanner;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author zxg
 *
 * 自动加载全局Feign接口，进行全局降级熔断配置
 *
 * 参考地址1：https://juejin.cn/post/6933072764395847693
 * 仓靠地址2：https://blog.csdn.net/dawnsun2013/article/details/124328651
 */

@Component
@Slf4j
public class AutoLoadFeignFuseConfig implements ApplicationRunner , EnvironmentAware {
    @Autowired
    private NacosConfigManager nacosConfigManager;

    private Environment environment;

    @Value("${spring.application.name}")
    private String applicationName;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    private static final String GROUP_ID = "DEFAULT_GROUP";
    private static final String DATA_ID = "FEIGN_RULES";
    private final static String HTTP_PROTOCOL_PREFIX = "http://";
    private static final String DYNAMIC_VALUE_PREFIX = "${";
    private static final String DYNAMIC_VALUE_SUFFIX = "}";

    private String getDataId(){
        return applicationName + "_" + DATA_ID;
    }

    /**
     * 加载项目中feign，初始化降级规则
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 获取启动类对象
        Class<?> mainClass = deduceMainApplication();

        log.info("==============开始加载Feign默认规则");
        if (mainClass == null){
            throw new RuntimeException("没有发现main主程序类");
        }

        EnableFeignClients enableFeignClients = mainClass.getAnnotation(EnableFeignClients.class);

        // 定义feign所在文件夹目录
        String[] feignClientPackages;

        if (enableFeignClients != null){
            String[] feignClientDeclaredPackages = enableFeignClients.basePackages();
            // 声明feign的包名
            if (feignClientDeclaredPackages.length == 0){
                feignClientPackages = new String[]{mainClass.getPackage().getName()};
            }else {
                feignClientPackages = feignClientDeclaredPackages;
            }
            // 初始化降级规则
            initDegradeRule(feignClientPackages);
        }

        log.info("==============Feign降级处理完成");
    }

    /**
     * 获取启动类对象
     */
    private Class<?> deduceMainApplication(){
        try {
            StackTraceElement[] stackTraceElements = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTraceElements) {
                if ("main".equals(stackTraceElement.getMethodName())){
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException e) {
            log.warn("获取启动类对象失败：" + e.getMessage());
        }

        return null;
    }

    /**
     * 初始化降级规则
     * @param feignClientPackages feign所在包数组
     */
    private void initDegradeRule(String[] feignClientPackages) {
        List<DegradeRule> localDegradeRuleList = new ArrayList<>();
        Set<Class> feignClientClazz = getFeignClientClazz(feignClientPackages);

        // 生成本地配置
        for (Class clientClazz : feignClientClazz) {
            List<DegradeRule> rules = initRules(clientClazz);
            localDegradeRuleList.addAll(rules);
        }

        // 获取nacos上远程规则
        List<DegradeRule> remoteDegradeRuleList = fetchRemoteRules();

        // 若远程配置不存在，直接上传本地配置
        if (remoteDegradeRuleList == null || remoteDegradeRuleList.isEmpty()){
            // 上传配置到nacos
            pushRules(localDegradeRuleList);
            return;
        }

        // 本地规则 合并 远程规则策略
        proess(localDegradeRuleList, remoteDegradeRuleList);
        // 上传配置到nacos
        pushRules(localDegradeRuleList);
    }

    /**
     * 获取nacos上远程规则
     */
    private List<DegradeRule> fetchRemoteRules() {
        try {
            return JSONObject.parseArray(nacosConfigManager.getConfigService().getConfig(getDataId(), GROUP_ID, 10000L), DegradeRule.class);
        } catch (NacosException e) {
            log.error("获取远程Feign配置失败：" + e.getErrMsg());
        }
        return new ArrayList<>();
    }

    /**
     * 上传配置到nacos
     */
    private void pushRules(List<DegradeRule> rules){
        try {
            String contentStr = JSONUtil.toJsonStr(rules);
            nacosConfigManager.getConfigService().publishConfig(getDataId(), GROUP_ID, contentStr);
        } catch (NacosException e) {
            log.error("上传Feign降级熔断配置信息到nacos失败");
        }
    }

    /**
     * 本地规则 合并 远程规则策略
     * @param localDegradeRuleList 本地配置
     * @param remoteDegradeRuleList 远程配置
     */
    private void proess(List<DegradeRule> localDegradeRuleList, List<DegradeRule> remoteDegradeRuleList) {
        for (DegradeRule rule : remoteDegradeRuleList) {
            if (localDegradeRuleList.contains(rule)) {
                DegradeRule ldr = localDegradeRuleList.get(localDegradeRuleList.indexOf(rule));
                if (ldr.equals(rule)) {
                    continue;
                }
                localDegradeRuleList.remove(ldr);
                localDegradeRuleList.add(rule);
            } else {
                localDegradeRuleList.add(rule);
            }
        }
    }

    /**
     * 扫描Feign的包路径，获取类对象
     * @param packageNames 包路径
     */
    private Set<Class> getFeignClientClazz(String[] packageNames){
        Set<Class> feignClientClazz = new HashSet<>();
        for (String packageName : packageNames) {
            packageName = packageName.replace(".**","");
            Set<Class<?>> classes = ClassScanner.scanAllPackageByAnnotation(packageName, FeignClient.class);
            feignClientClazz.addAll(classes);
        }

        return feignClientClazz;
    }

    /**
     * 初始化规则
     */
    public List<DegradeRule> initRules(Class clazz){
        List<DegradeRule> degradeRuleList = new ArrayList<>();
        FeignClient feignClient = (FeignClient) clazz.getAnnotation(FeignClient.class);

//        String url = feignClient.url();
//        String serverName = "";
//        if (StringUtils.isNotBlank(url)){
//            if (url.startsWith(DYNAMIC_VALUE_PREFIX) && url.endsWith(DYNAMIC_VALUE_SUFFIX)){
//                url = this.environment.resolvePlaceholders(url);
//            }
//        }else {
//            serverName = feignClient.name();
//        }

        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
//            degradeRuleList.add(buildDegradeRule(getResourceName(url, serverName, method)));
            degradeRuleList.add(buildDegradeRule(getResourceName(method)));
        }

        DegradeRuleManager.loadRules(degradeRuleList);
        return degradeRuleList;
    }

    /**
     * 获取资源服务器名
     */
    private String getResourceName(Method method) {
        String resourceName = "";
        RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
        if (null != methodRequestMapping) {
            String mrm = methodRequestMapping.value()[0];
            resourceName = (mrm.startsWith("/") ? mrm : "/" + mrm);
        }

        GetMapping methodGetMapping = method.getAnnotation(GetMapping.class);
        if (null != methodGetMapping) {
            String mpm = methodGetMapping.value()[0];
            resourceName = (mpm.startsWith("/") ? mpm : "/" + mpm);
        }

        PostMapping methodPostMapping = method.getAnnotation(PostMapping.class);
        if (null != methodPostMapping) {
            String mpm = methodPostMapping.value()[0];
            resourceName = (mpm.startsWith("/") ? mpm : "/" + mpm);
        }
        return resourceName;
    }

    /**
     * 构建熔断规则
     */
    private DegradeRule buildDegradeRule(String resourceName) {
        DegradeRule rule = new DegradeRule();
        //设置资源名
        rule.setResource(resourceName);
        //设置降级规则 TR 10 ms
        rule.setCount(200);
        // 规则类型 RT
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        // 窗口时间
        rule.setTimeWindow(10);
        rule.setMinRequestAmount(3);
        rule.setStatIntervalMs(30000);
        rule.setSlowRatioThreshold(0.6);
        return rule;
    }


}
